package net.crisps.cloud.log.aspect;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import net.crisps.cloud.log.annotation.OperateLog;
import net.crisps.cloud.log.service.CustomLogListener;
import net.crisps.cloud.log.service.GetOperatorService;
import net.crisps.cloud.log.service.LogService;
import net.crisps.cloud.log.util.RequestUtils;
import net.crisps.cloud.log.util.SpringUtil;
import net.crisps.cloud.log.vo.LogVo;
import net.crisps.cloud.log.vo.OperatorVo;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.*;
import org.springframework.expression.common.CompositeStringExpression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.common.TemplateAwareExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;


/**
 * 业务和日志切面，管理所有业务和日志的切面。
 *
 * @Author tanghm
 * @Date 2022年03月26日 10:32
 */
@Component
@Aspect
@Slf4j
public class BusinessLogAspect  {

    @Autowired(required = false)
    private LogService logService;

    @Autowired(required = false)
    private GetOperatorService getOperatorService;

    @Autowired(required = false)
    private CustomLogListener customLogListener;

    private static final String FULL_NAME_SPIL = ".";

    private final StandardEvaluationContext context = new StandardEvaluationContext();

    SpelExpressionParser parser = new SpelExpressionParser();

    LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    public static final TemplateParserContext templateContext = new TemplateParserContext("{","}");

    /**
     * 控制层所有OperateLog方法
     */
    @Pointcut("@annotation(net.crisps.cloud.log.annotation.OperateLog) || @annotation(net.crisps.cloud.log.annotation.OperateLogs)")
    public void logOperate() {
    }

    @Around("logOperate()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable{
        Object result;
        List<LogVo> logS = new ArrayList<>();
        StopWatch stopWatch = new StopWatch();
        try{
            logS = resolveExpress(joinPoint);
            stopWatch.start();
            result = joinPoint.proceed();
            stopWatch.stop();
            logS.forEach(logVo -> {
                logVo.setIsSuccess(true);
                logVo.setReturnStr(JSON.toJSONString(result));
            });
        }catch (Throwable throwable) {
            stopWatch.stop();
            // 方法执行异常，自定义上下文未写入，但是仍然需要写入其他变量
            logS = resolveExpress(joinPoint);
            logS.forEach(logVo -> {
                logVo.setIsSuccess(false);
                logVo.setExceptionMessage(ExceptionUtil.stacktraceToString(throwable));
            });
            throw throwable;
        }finally {
            logS.forEach(logVo -> {
                try {
                    // 记录执行时间
                    logVo.setExecutTime(stopWatch.getTotalTimeMillis());
                    // 设置操作人id，操作人名称，操作工号，操作时间
                    OperatorVo operatorVo = getOperatorService.getOperator();
                    Optional.ofNullable(operatorVo).ifPresent(t->{
                        logVo.setOperatorId(t.getId());
                        logVo.setOperatorName(t.getName());
                        logVo.setOperatorNo(t.getUserCenterNo());
                    });
                    HttpServletRequest request = getRequest();
                    logVo.setMethod(request.getRequestURI());
                    logVo.setParams(RequestUtils.getParams(request));
                    logVo.setSysCode(request.getHeader("sysCode"));
                    logVo.setOperateDate(LocalDateTime.now());
                    // 发送本地监听
                    if (customLogListener != null) {
                        customLogListener.createLog(logVo);
                    }
                    // 发送给消息队列
//                    if (logService != null) {
//                        logService.createLog(logVo);
//                    }
                    // 发送给kafka消息， 全部萍
                } catch (Throwable throwable) {
                    log.error("SystemLogAspect doAround send log error", throwable);
                }
            });
        }
        return result;
    }


    public List<LogVo> resolveExpress(JoinPoint joinPoint) throws Throwable {
        try {
            Object[] args = joinPoint.getArgs();
            HttpServletRequest request = getRequest();
            List<LogVo> logVoList = new ArrayList<>();
            Method method = getMethod(joinPoint);
            OperateLog[] annotations = method.getAnnotationsByType(OperateLog.class);

            for (OperateLog annotation : annotations) {
                LogVo logVo = new LogVo();
                String bizId = annotation.bizId();
                String bizType = annotation.bizType();
                String title = annotation.title();
                String content = annotation.description();
                String before = annotation.before();
                String after = annotation.after();
                try {
                    String[] params = discoverer.getParameterNames(method);
                    for (int len = 0; len < params.length; len++) {
                        context.setVariable(params[len], args[len]);
                    }
                    bizId = parseAndResult(bizId);
                    content = parseAndResult(content);
                    before = parseAndResult(before);
                    after = parseAndResult(after);
                    log.info("====》bizId:{},content:{},before:{},after:{}", bizId, content, before, after);
                } catch (Exception e) {
                    log.error("SystemLogAspect resolveExpress error", e);
                } finally {
                    logVo.setIp(RequestUtils.getRemoteIpAddress(request));
                    logVo.setBizId(bizId);
                    logVo.setOperateContent(content);
                    logVo.setOperateTitle(title);
                    logVo.setOperateDate(LocalDateTime.now());
                    logVo.setBeforeStr(before);
                    logVo.setAfterStr(after);
                    logVo.setBizType(bizType);
                }

                logVoList.add(logVo);
            }
            return logVoList;

        } catch (Exception e) {
            log.error("SystemLogAspect resolveExpress error", e);
            return new ArrayList<>();
        }
    }

    /**
     * 解析spel并获取结果
     *
     * @param spelStr
     * @return
     */
    public String parseAndResult(String spelStr){
        if(spelStr.contains("func_")) {
            if(StringUtils.isBlank(spelStr)) return null;
            String start = spelStr.substring(0,spelStr.indexOf("func_"));
            String end = spelStr.substring(spelStr.indexOf("func_") +5);
            start  = parseSpel(context, start, String.class);
            end = parseFunction(end);
            spelStr = start + end;
        } else {
            spelStr = parseSpel(context, spelStr, String.class);
        }
        return spelStr;
    }

    /**
     * 解析spel 表达式
     *
     * @param spel      表达式
     * @param clazz     返回结果的类型
     * @return 执行spel表达式后的结果
     */
    private <T> T parseSpel(EvaluationContext context, String spel, Class<T> clazz) {
        try {
            Expression expression = parser.parseExpression(spel, templateContext);
            return expression.getValue(context, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 解析函数，带有括号()的
     *
     * @param spel      表达式
     * @return 执行spel表达式后的结果
     */
    private String parseFunction(String spel) {
        //这里使用自定义的解析器，默认的遇到method(#p1,#p2)多参数会报错
        Expression expression = methodArgsExpressionParser.parseExpression(spel, new TemplateParserContext("(",")"));
        if (!(expression instanceof CompositeStringExpression)) {
            throw new ExpressionException(String.format("expressionString [{%s}] incongruity for CustomMethod expression", expression));
        }
        CompositeStringExpression compositeStringExpression = (CompositeStringExpression) expression;
        Expression[] expressions = compositeStringExpression.getExpressions();

        //方法全名
        String functionName = expressions[0].getExpressionString();
        //参数解析并转换
        String argsExpression = expressions[1].getExpressionString();
        List<Object> objectList = new ArrayList<>();
        String[] args = argsExpression.split(",");
        for (int i = 0; i < args.length; i++) {
            objectList.add(parseSpel(context, args[i], Object.class));
        }

        int indexOf = functionName.lastIndexOf(FULL_NAME_SPIL);
        String classFullName = functionName.substring(0, indexOf);
        String methodName = functionName.substring(indexOf + 1);

        Object object = SpringUtil.getBean(classFullName);
        Method method = resolve(object, methodName, objectList.toArray());
        Object obj = ReflectionUtils.invokeMethod(method, object, objectList.toArray());

        // 属性
        if(expressions.length == 3) {
            String filed = expressions[2].getExpressionString();
            filed = filed.substring(1);
            Field field = ReflectionUtils.findField(obj.getClass(), filed);
            field.setAccessible(true);
            return (String)ReflectionUtils.getField(field, obj);
        }
        return JSON.toJSONString(obj);
    }

    public Method resolve(Object targetObject, String functionName, Object[] args) {
        Class<?> targetClass = targetObject.getClass();
        Method method;
        Class[] paramsType;
        paramsType = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            paramsType[i] = args[i].getClass();
        }
        method = ReflectionUtils.findMethod(targetClass, functionName, paramsType);
        if (method == null) {
            method = ReflectionUtils.findMethod(targetClass, functionName, java.io.Serializable.class);
            if (method == null) {
                throw new EvaluationException(String.format("not found method [%s] param %s for class [%s]", functionName, JSON.toJSONString(paramsType), targetObject.getClass().getName()));
            }
        }
        return method;
    }

    private static final MethodArgsExpressionParser methodArgsExpressionParser = new MethodArgsExpressionParser();
    /**
     * 方法参数解析器
     */
    static class MethodArgsExpressionParser extends TemplateAwareExpressionParser {
        @Override
        protected Expression doParseExpression(String expressionString, ParserContext context) throws ParseException {
            return new LiteralExpression(expressionString);
        }
    }

    /**
     * 获取当前切面的请求体
     *
     * @return 返回当前切面的请求体
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes.getRequest();
    }

    protected Method getMethod(JoinPoint joinPoint) {
        Method method = null;
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature ms = (MethodSignature) signature;
            Object target = joinPoint.getTarget();
            method = target.getClass().getMethod(ms.getName(), ms.getParameterTypes());
        } catch (NoSuchMethodException e) {
            log.error("SystemLogAspect getMethod error", e);
        }
        return method;
    }

}